package jastgen.frontend;

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;

import jastgen.nodes.ClassDef;
import jastgen.nodes.Field;
import jastgen.nodes.Import;
import jastgen.nodes.InterfaceDef;
import jastgen.nodes.Module;
import jastgen.nodes.Type;
import jastgen.CompileException;
import jastgen.UncheckedCompileException;
import jastgen.frontend.ByCaper.SemanticAction;

public class ASTBuilder implements SemanticAction {

	private final Scanner in;

	private final Map<String, Integer> interfaceNums =
		new HashMap<String, Integer>();

	public ASTBuilder(
			final Scanner in)
	{
		this.in = in;
	}

	@Override
	public Module buildModule(
			final List namespace,
			final List imports,
			final List xs)
	{
		// split InterfaceDef and ClassDef.
		final ArrayList<InterfaceDef> is = new ArrayList<InterfaceDef>(xs.size());
		final ArrayList<ClassDef> cs = new ArrayList<ClassDef>(xs.size());
		for(final Object x : xs) {
			if(x instanceof InterfaceDef) {
				is.add((InterfaceDef)x);
			} else if(x instanceof ClassDef) {
				cs.add((ClassDef)x);
			} else {
				throw new RuntimeException(
						"Neither InterfaceDef nor ClassDef"
						+ " (maybe JASTgen is broken) .");
			}
		}

		return new Module(namespace, imports, is, cs);
	}

	@Override
	public InterfaceDef buildInterfaceDef(
			final Type name,
			final List fields)
	{
		for(final Field f : (List<Field>)fields) {
			if(f.hasBody())
				throw new UncheckedCompileException(
						"Method 'get" + f.getNameInMethod() + "();'"
						+ " of interface '" + name + "' has body!"
						+ " (line " + in.getLineNum() + ")");
		}
		return new InterfaceDef(name, fields);
	}

	@Override
	public ClassDef buildClassDef(
			final Type name,
			final List fields)
	{
		return new ClassDef(name, null, 0, fields);
	}

	@Override
	public ClassDef buildClassDef(
			final Type name,
			final Type interfaceName,
			final List fields)
	{
		return new ClassDef(
				name,
				interfaceName,
				interfaceNum(interfaceName.getName()),
				fields);
	}

	private int interfaceNum(
			final String interfaceName)
	{
		final int n;
		if(interfaceNums.containsKey(interfaceName))
			n = interfaceNums.get(interfaceName);
		else
			n = 0;
		interfaceNums.put(interfaceName, n+1);
		return n;
	}

	@Override
	public Field buildField(
			final Type type,
			final boolean capitalizeAll,
			final String name)
	{
		return new Field(
				type,
				name,
				capitalizeAll);
	}

	@Override
	public Field buildField(
			final Type type,
			final boolean capitalizeAll,
			final String name,
			final String methodBody)
	{
		return new Field(
				type,
				name,
				capitalizeAll,
				remove1stAndLast(methodBody));
	}

	private static String remove1stAndLast(
			final String s)
	{
		return s.substring(1, s.length()-1);
	}

	@Override
	public Import buildImport(
			final boolean isStatic,
			final List body)
	{
		return new Import(isStatic, body);
	}

	@Override
	public List buildList() {
		return new LinkedList();
	}

	@Override
	public List buildList(
			final List list,
			final Field elem)
	{
		list.add(elem);
		return list;
	}

	@Override
	public List buildList(
			final List list,
			final Import elem)
	{
		list.add(elem);
		return list;
	}

	@Override
	public List buildList(
			final String elem)
	{
		return buildList(new LinkedList(), elem);
	}

	@Override
	public List buildList(
			final List list,
			final String elem)
	{
		list.add(elem);
		return list;
	}

	@Override
	public List buildList(
			final Type elem)
	{
		return buildList(new LinkedList(), elem);
	}

	@Override
	public List buildList(
			final List list,
			final Type elem)
	{
		list.add(elem);
		return list;
	}

	@Override
	public List buildList(
			final Object elem)
	{
		return buildList(new LinkedList(), elem);
	}

	@Override
	public List buildList(
			final List list,
			final Object elem)
	{
		list.add(elem);
		return list;
	}

	@Override
	public Type buildTypeDef(
			final String name)
	{
		return new Type(name);
	}

	@Override
	public Type buildTypeDef(
			final String base,
			final List params)
	{
		return new Type(base, params);
	}

	@Override
	public List through(
			final List x)
	{
		return x;
	}

	@Override
	public Object through(
			final ClassDef x)
	{
		return x;
	}

	@Override
	public Object through(
			final InterfaceDef x)
	{
		return x;
	}

	@Override
	public boolean buildTrue() {
		return true;
	}

	@Override
	public boolean buildFalse() {
		return false;
	}

	// never called
	@Override
	public void stackOverflow() {
	}

	@Override
	public void syntaxError() {
		/*
		throw new UncheckedCompileException(
				"Parse error at line "
				+ in.getLineNum() + ".");
		*/
		try {
			throw new UncheckedCompileException(
					"Parse error at line "
					+ in.getLineNum() + ". (" + in.getNextToken() + ")");
		} catch(final CompileException e) {
			throw new UncheckedCompileException(e);
		}
	}
}
